﻿using System;
using System.Linq;
using Microsoft.Practices.Prism.Commands;
using System.Collections.ObjectModel;
using Cartoons.Books.Model;
using System.Collections.Generic;
using System.Windows.Data;
using System.Windows;
using System.Windows.Printing;
using System.Threading;

namespace Cartoons.ViewModels
{
    public class MainPageViewModel : ViewModelBase
    {
        private List<Book> booksCached;
        private List<int> booksIDs;
        private List<int> filterOrder;
        public MainPageViewModel()
        {
            FilteredBooks = new ObservableCollection<Book>();
            booksCached = new List<Book>();
            booksIDs = new List<int>();
            filterOrder = new List<int>();
            IsLoading = true;

            MainFilters = new FilterViewModel[] {
                new TextFilterViewModel("BookName", () => FilteredBooks.Select(b => b.BookName).ToArray()) { DisplayName = "Назва" },
                new TextInCollectionFilterViewModel("Authors.Name", () => FilteredBooks.SelectMany(b => b.Authors).Select(a => a.Name).Distinct().ToArray()) { DisplayName = "Автори" },
                new RangeFilterViewModel("PublicationYear", () => FilteredBooks.Select(b=>b.PublicationYear).ToArray()) { DisplayName = "Рік видання" },
                new TextFilterViewModel("PublicationPlace", () => FilteredBooks.Select(b => b.PublicationPlace).Distinct().ToArray()) { DisplayName = "Місце видання" },
                new TextFilterViewModel("Publisher", () => FilteredBooks.Select(b => b.Publisher).Distinct().ToArray()) { DisplayName = "Видавництво" },
                new CategoryFilterViewModel("LanguageID", () => FilteredBooks.Select(b => b.Language).Distinct().ToArray()) { DisplayName = "Мова" }
            };

            CloseFilter = new DelegateCommand<FilterViewModel>(f =>
                {
                    if (f != null)
                        f.Reset();

                    SelectedFilter = null;
                });

            ApplyFilters = new DelegateCommand<FilterViewModel>(filter =>
                {
                    SelectedFilter = null;
                    ApplyFilter(filter);
                });

            ClearFilter = new DelegateCommand<FilterViewModel>(f =>
                {
                    if (f != null)
                    {
                        f.Reset();
                        var filterIndex = Array.IndexOf(MainFilters.ToArray(), f);

                        if (filterOrder.Contains(filterIndex))
                            filterOrder.Remove(filterIndex);
                        ApplyFilters.Execute(null);
                    }
                });

            Print = new DelegateCommand<UIElement>(el =>
                {
                    PrintDocument doc = new PrintDocument();
                    doc.PrintPage += (o, e) =>
                        {
                            e.PageVisual = el;
                            e.HasMorePages = false;
                        };
                    doc.Print(SelectedBook.BookName);
                });

            UnselectBook = new DelegateCommand<string>(u =>
                {
                    SelectedBook = null;
                });

            var svcProxy = new BookServiceClient();

            svcProxy.GetBooksCompleted += (o, e) =>
            {
                booksIDs.AddRange(e.Result);

                LoadBooksAsync();
            };

            svcProxy.GetBooksAsync();

        }

        private void ApplyFilter(FilterViewModel filter)
        {
            if (filter != null)
            {
                //фільтри потрібно застосовувати в тому порядку, в якому їх вносив користувач. Визначаємо порядок:
                var filterIndex = Array.IndexOf(MainFilters.ToArray(), filter);

                if (!filterOrder.Contains(filterIndex))
                    filterOrder.Add(filterIndex);
            }
            var filters = MainFilters.ToArray();

            var filterGroups = filterOrder.Select(index => filters[index]).Select(f => f.Filters);

            IEnumerable<Book> books = booksCached;

            foreach (var filterGroup in filterGroups)
            {
                var tmpBooks = new Book[] { };
                foreach (var f in filterGroup)
                    tmpBooks = tmpBooks.Concat(f.Apply(books)).Distinct().ToArray();
                books = tmpBooks;
            }

            foreach (var book in FilteredBooks.ToArray())
                if (!books.Contains(book))
                    FilteredBooks.Remove(book);

            foreach (var book in books)
            {
                if (!FilteredBooks.Contains(book))
                    FilteredBooks.Add(book);
            }

            FilteredBooksTitles = FilteredBooks.Select(b => b.TitlePageIndex).ToArray();
            RaisePropertyChanged("FilteredBooksTitles");

            foreach (var f in MainFilters)
                f.LoadData();
        }

        private void LoadBooksAsync()
        {
            Action<int, Book[]> reseiveData = null;
            reseiveData = (position, books) =>
                {
                    if (books.Length > 0)
                    {
                        position += books.Length;
                        DisplayDataChunk(books);

                        var svc = new BookServiceClient();
                        svc.LoadBooksByIDCompleted += (o, e) =>
                            {
                                reseiveData(position, e.Result.ToArray());
                            };

                        svc.LoadBooksByIDAsync(new System.Collections.ObjectModel.ObservableCollection<int>(booksIDs.Skip(position).Take(100).ToArray()));
                    }
                    else
                        ProcessAllDataReseived();
                };

            var svc1 = new BookServiceClient();
            svc1.LoadBooksByIDCompleted += (o, e) =>
                {
                    reseiveData(0, e.Result.ToArray());
                };

            svc1.LoadBooksByIDAsync(new System.Collections.ObjectModel.ObservableCollection<int>(booksIDs.Take(10).ToArray()));
        }

        private void ProcessAllDataReseived()
        {
            foreach (var filter in MainFilters)
                filter.LoadData();
        }

        private void DisplayDataChunk(Book[] books)
        {
            IsLoading = false;
            booksCached.AddRange(books);

            ApplyFilter(null);
        }

        #region Properties

        public ObservableCollection<Book> FilteredBooks { get; private set; }
        public short[] FilteredBooksTitles { get; private set; }
        public FilterViewModel[] MainFilters { get; private set; }

        private FilterViewModel selectedFilter;
        public FilterViewModel SelectedFilter
        {
            get { return selectedFilter; }
            set { selectedFilter = value; RaisePropertyChanged("SelectedFilter"); }
        }
        private bool isLoading;

        public bool IsLoading
        {
            get { return isLoading; }
            set { isLoading = value; RaisePropertyChanged("IsLoading"); }
        }

        private short? selectedBookTitle;
        public short? SelectedBookTitle
        {
            get { return selectedBookTitle; }
            set
            {
                if (selectedBookTitle == value)
                    return;

                selectedBookTitle = value;
                RaisePropertyChanged("SelectedBookTitle");

                if (value != null)
                    SelectedBook = booksCached.First(b => b.TitlePageIndex == value.Value);
                else
                    SelectedBook = null;
                RaisePropertyChanged("SelectedBook");
            }
        }
        private Book selectedBook;
        public Book SelectedBook
        {
            get { return selectedBook; }
            set
            {
                selectedBook = value;
                RaisePropertyChanged("SelectedBook");
                if (value != null)
                    SelectedBookTitle = value.TitlePageIndex;
                else
                    SelectedBookTitle = null;
            }
        }

        #endregion

        #region Commands

        public DelegateCommand<FilterViewModel> ApplyFilters { get; private set; }
        public DelegateCommand<FilterViewModel> ClearFilter { get; private set; }
        public DelegateCommand<FilterViewModel> CloseFilter { get; private set; }
        public DelegateCommand<UIElement> Print { get; private set; }
        public DelegateCommand<string> UnselectBook { get; private set; }

        #endregion

        #region Filters

        public class FilterViewModel : ViewModelBase
        {
            public string FilterPropertyName { get; private set; }
            private Func<object> loadData;

            public FilterViewModel(string filterPropertyName, Func<object> loadData)
            {
                IsLoaded = false;
                IsDataLoading = true;
                this.loadData = loadData;
                FilterPropertyName = filterPropertyName;
            }

            public IEnumerable<BookFilter> Filters { get { RaisePropertyChanged("IsApplied"); return GetFilters(); } }
            protected virtual IEnumerable<BookFilter> GetFilters()
            {
                return new EmptyFilter[] { new EmptyFilter(FilterPropertyName) };
            }

            private bool isLoaded;
            public bool IsLoaded
            {
                get { return isLoaded; }
                set { isLoaded = value; RaisePropertyChanged("IsLoaded"); }
            }

            private bool isDataLoading;
            public bool IsDataLoading
            {
                get { return isDataLoading; }
                set { isDataLoading = value; RaisePropertyChanged("IsDataLoading"); }
            }

            public void LoadData()
            {
                this.IsDataLoading = false;
                this.IsLoaded = true;
                RaisePropertyChanged("IsApplied");
                ResieveDataOverride(loadData());
            }

            public virtual void Reset()
            {
                RaisePropertyChanged("IsApplied");
            }

            protected virtual void ResieveDataOverride(object data) { }

            public bool IsApplied { get { return GetFilters().Where(f => !(f is EmptyFilter)).Count() > 0; } }

            public string DisplayName { get; set; }

        }

        public class TextFilterViewModel : FilterViewModel
        {
            private string[] words;

            public TextFilterViewModel(string filterPropertyName, Func<object> loadData)
                : base(filterPropertyName, loadData)
            {
                Words = new ObservableCollection<string>();
                Text = "";
            }

            private string text;
            public string Text
            {
                get { return text; }
                set
                {
                    text = value;
                    RaisePropertyChanged("Text");
                }
            }

            protected override void ResieveDataOverride(object data)
            {
                if (data == null)
                    return;

                var words = data as string[];
                this.words = words;
                Words.Clear();
                foreach (var word in words.Distinct().OrderBy(w => w))
                    Words.Add(word);
            }

            public override void Reset()
            {
                base.Reset();
                this.Text = "";
            }

            public ObservableCollection<string> Words { get; private set; }
            protected override IEnumerable<BookFilter> GetFilters()
            {
                if (!string.IsNullOrEmpty(Text))
                    return new ScalarTextPropertyFilter[] { new ScalarTextPropertyFilter(FilterPropertyName, Text) };
                return base.GetFilters();
            }
        }

        public class TextInCollectionFilterViewModel : TextFilterViewModel
        {
            public TextInCollectionFilterViewModel(string filterPropertyName, Func<object> loadData)
                : base(filterPropertyName, loadData) { }

            protected override IEnumerable<BookFilter> GetFilters()
            {
                if (!string.IsNullOrEmpty(Text))
                    return new CollectionPropertyAnyFilter[] { new CollectionPropertyAnyFilter(FilterPropertyName, Text) };
                return base.GetFilters();
            }
        }

        public class RangeFilterViewModel : FilterViewModel
        {
            public RangeFilterViewModel(string filterPropertyName, Func<object> loadData)
                : base(filterPropertyName, loadData)
            {
                Ranges = new ObservableCollection<Range>();
            }

            public override void Reset()
            {
                base.Reset();

                Left = null;
                Right = null;
                SelectedRange = null;
            }

            private double? left;

            public double? Left
            {
                get { return left; }
                set
                {
                    left = value;
                    RaisePropertyChanged("Left");
                    if (SelectedRange != null)
                    {
                        if (value != SelectedRange.Left)
                            SelectedRange = null;
                    }
                }
            }

            private double? right;

            public double? Right
            {
                get { return right; }
                set
                {
                    right = value;
                    RaisePropertyChanged("Right");
                    if (SelectedRange != null)
                    {
                        if (value != SelectedRange.Right)
                            SelectedRange = null;
                    }
                }
            }

            private Range selectedRange;

            public Range SelectedRange
            {
                get
                {
                    return selectedRange;
                }
                set
                {
                    selectedRange = value;
                    RaisePropertyChanged("SelectedRange");
                    if (value != null)
                    {
                        Left = value.Left;
                        Right = value.Right;
                    }

                }
            }

            public ObservableCollection<Range> Ranges { get; private set; }

            protected override void ResieveDataOverride(object data)
            {
                base.ResieveDataOverride(data);
                if (data == null)
                    return;

                List<double> numbs = new List<double>();

                foreach (var item in (data as Array))
                    if (item != null) numbs.Add(Convert.ToDouble(item));

                var min = (int)numbs.Min() / 100 * 100;

                var max = ((int)numbs.Max() / 100 + 1) * 100;


                Ranges.Clear();

                for (int i = 0; min + i * 100 < max; i++)
                    Ranges.Add(new Range() { Left = min + 100 * i, Right = min + 100 * (i + 1) });
            }

            protected override IEnumerable<BookFilter> GetFilters()
            {
                if (Left == null && Right == null)
                    return base.GetFilters();

                if (Left == null)
                    return new RangeFilter[] { new RangeFilter(FilterPropertyName, double.NegativeInfinity, Right.Value) };

                if (Right == null)
                    return new RangeFilter[] { new RangeFilter(FilterPropertyName, Left.Value, double.PositiveInfinity) };

                return new RangeFilter[] { new RangeFilter(FilterPropertyName, Left.Value, Right.Value) };
            }

            public class Range
            {
                public double Left { get; set; }
                public double Right { get; set; }
            }
        }

        public class CategoryFilterViewModel : FilterViewModel
        {
            private List<Selectable<IOptionsProvider>> selectedOptions;
            public CategoryFilterViewModel(string filterPropertyName, Func<object> loadData)
                : base(filterPropertyName, loadData)
            {
                Options = new ObservableCollection<Selectable<IOptionsProvider>>();
                selectedOptions = new List<Selectable<IOptionsProvider>>();
            }

            public override void Reset()
            {
                base.Reset();
                foreach (var option in selectedOptions)
                    option.IsSelected = false;

                foreach (var option in Options)
                    option.IsSelected = false;
            }

            protected override void ResieveDataOverride(object data)
            {
                base.ResieveDataOverride(data);
                if (data == null)
                    return;

                Options.Clear();

                //foreach (var option in (data as IEnumerable<IOptionsProvider>).Distinct(new OptionEqualityComperer()).OrderBy(o=>o.Key))
                //    Options.Add(new Selectable<IOptionsProvider>(option));

                //foreach (var option in selectedOptions)
                //{
                //    var opt = Options.SingleOrDefault(o => o.Item.Key == option.Item.Key);
                //    if (opt != null)
                //        opt.IsSelected = option.IsSelected;
                //}
            }

            public ObservableCollection<Selectable<IOptionsProvider>> Options { get; private set; }

            protected override IEnumerable<BookFilter> GetFilters()
            {
                selectedOptions.Clear();
                selectedOptions.AddRange(Options);
                if (!Options.Any(o => o.IsSelected))
                    return base.GetFilters();
                return Options.Where(o => o.IsSelected).Select(o => new CategoryFilter(FilterPropertyName, o.Item)).ToArray();
            }
        }

        #endregion
    }

    public class RelativeToAbsoluteURIConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            var rel = value as string;
            if (rel == null)
                return null;

            return new Uri(new Uri(@"http://rarebooks.univ.net.ua/", UriKind.Absolute), @"Books/" + rel);
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}
